﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace dPet.Application.Services.Implementation
{
	using AutoMapper;
	using dPet.Application.DataObjects;
	using dPet.Application.QueryObjects;
	using dPet.Domain.Models;
	using dPet.Domain.Repositories;
	using dPet.Domain.Services;
	using dPet.Domain.Specifications;
	using dPet.Infrastructure.Logging;
	using Hangerd;
	using Hangerd.Components;
	using Hangerd.Extensions;
	using Hangerd.Repository;

	public class InventoryService : ServiceBase, IInventoryService
	{
		#region Repository

		private readonly IHospitalRepository _hospitalRepository;
		private readonly IProductRepository _productRepository;
		private readonly IInventoryBatchRepository _inventoryBatchRepository;
		private readonly IInventoryLORecordRepository _inventoryLORecordRepository;
		private readonly IInventoryAlarmItemRepository _inventoryAlarmItemRepository;

		#endregion

		#region Services

		private readonly IInvoicingDomainService _invoicingDomainService;

		#endregion

		#region Constructors

		public InventoryService(
			IRepositoryContext unitOfWork,
			IHospitalRepository hospitalRepository,
			IProductRepository productRepository,
			IInventoryBatchRepository inventoryBatchRepository,
			IInventoryLORecordRepository inventoryLORecordRepository,
			IInventoryAlarmItemRepository inventoryAlarmItemRepository,
			IInvoicingDomainService invoicingDomainService)
			: base(unitOfWork)
		{
			_hospitalRepository = hospitalRepository;
			_productRepository = productRepository;
			_inventoryBatchRepository = inventoryBatchRepository;
			_inventoryLORecordRepository = inventoryLORecordRepository;
			_inventoryAlarmItemRepository = inventoryAlarmItemRepository;
			_invoicingDomainService = invoicingDomainService;
		}

		#endregion

		#region Inventory

		public IEnumerable<InventoryBatchDto> GetInventoryBatches(string hospitalId, InventoryBatchQueryDto queryDto,
			int pageIndex, int pageSize, out int totalNumber)
		{
			var spec = InventoryBatchSpecifications.HospitalIdEquals(hospitalId);

			if (!string.IsNullOrWhiteSpace(queryDto.ProductBarcode))
			{
				var product = _productRepository.Get(ProductSpecifications.BarcodeEquals(queryDto.ProductBarcode), false);

				if (product == null)
				{
					totalNumber = 0;

					return new InventoryBatchDto[] { };
				}

				spec &= InventoryBatchSpecifications.DetailContainsProduct(product);
			}

			var batches = _inventoryBatchRepository.GetAll(spec, false)
				.OrderByDescending(c => c.CreateTime)
				.Paging(pageIndex, pageSize, out totalNumber);

			return Mapper.Map<IEnumerable<InventoryBatch>, IEnumerable<InventoryBatchDto>>(batches);
		}

		public InventoryBatchDto GetInventoryBatch(string batchId)
		{
			var batch = _inventoryBatchRepository.Get(batchId, false, ib => ib.Details);

			return Mapper.Map<InventoryBatch, InventoryBatchDto>(batch);
		}

		public MessagedResult<InventoryBatchDto> AddInventoryByBatch(string hospitalId, InventoryBatchDetailDto[] batchDetailDtos, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "库存管理", "商品入库", (ref InventoryBatch batch) =>
			{
				if (batchDetailDtos == null || batchDetailDtos.Length == 0)
				{
					throw new HangerdException("入库清单为空");
				}

				var hospital = _hospitalRepository.Get(hospitalId, false);

				if (hospital == null)
				{
					throw new HangerdException("医院信息不存在");
				}

				//创建入库批次
				batch = new InventoryBatch(hospital, @operator.LoginName);

				//添加入库明细
				foreach (var detail in batchDetailDtos)
				{
					batch.AddDetail(detail.ProductId, detail.Quantity, detail.ExpiryTime);
				}

				//商品入库
				_invoicingDomainService.AddInventoryByBatch(batch);

				_inventoryBatchRepository.Add(batch, true);
				
				UnitOfWork.Commit();

				return Mapper.Map<InventoryBatch, InventoryBatchDto>(batch);
			});
		}

		public IEnumerable<InventoryLORecordDto> GetInventoryLORecords(string hospitalId, InventoryLORecordQueryDto queryDto, int pageIndex, int pageSize, out int totalNumber)
		{
			var spec = InventoryLORecordSpecifications.HospitalIdEquals(hospitalId);

			if (!string.IsNullOrWhiteSpace(queryDto.ProductBarcode))
			{
				spec &= InventoryLORecordSpecifications.ProductBarcodeEquals(queryDto.ProductBarcode);
			}

			var records = _inventoryLORecordRepository.GetAll(spec, false)
				.OrderByDescending(ilor => ilor.LastModified)
				.Paging(pageIndex, pageSize, out totalNumber);

			return Mapper.Map<IEnumerable<InventoryLORecord>, IEnumerable<InventoryLORecordDto>>(records);
		}

		public MessagedResult<bool> ReportInventoryLO(string productId, string inventoryNumber, InventoryLOTypeDto loType, int quantity, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "库存管理", "报损报溢", (ref Product product) =>
			{
				product = _productRepository.Get(productId, true, p => p.Inventories);

				if (product == null)
				{
					throw new HangerdException("商品信息不存在");
				}

				switch (loType)
				{
					case InventoryLOTypeDto.Loss:
						product.ReportInventoryLoss(inventoryNumber, quantity, @operator.LoginName);
						break;
					case InventoryLOTypeDto.Overflow:
						product.ReportInventoryOverflow(inventoryNumber, quantity, @operator.LoginName);
						break;
					default:
						throw new HangerdException("报损报溢类型异常");
				}

				_productRepository.Update(product, false);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public void CheckProductInventory()
		{
			try
			{
				var spec = ProductSpecifications.EnableQuantityAlarm() | ProductSpecifications.EnableExpiryAlarm();
				var products = _productRepository.GetAll(spec, true, p => p.Inventories).ToList();

				foreach (var product in products)
				{
					product.CheckInventoryQuantity();
					product.CheckInventoryExpiry();
				}

				UnitOfWork.Commit();
			}
			catch (Exception ex)
			{
				LocalLoggingService.Exception("CheckProductInventory Error!");
				LocalLoggingService.Exception(ex);
			}
		}

		#endregion

		#region InventoryAlarm

		public IEnumerable<InventoryAlarmItemDto> GetInventoryAlarms(string hospitalId, int pageIndex, int pageSize, out int totalNumber)
		{
			var spec = InventoryAlarmItemSpecifications.HospitalIdEquals(hospitalId);

			var alarms = _inventoryAlarmItemRepository.GetAll(spec, false)
				.OrderByDescending(iai => iai.LastModified)
				.Paging(pageIndex, pageSize, out totalNumber);

			return Mapper.Map<IEnumerable<InventoryAlarmItem>, IEnumerable<InventoryAlarmItemDto>>(alarms);
		}

		public MessagedResult<bool> DisableInventoryAlarm(string productId, InventoryAlarmTypeDto alarmTypeDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "库存告警", "取消告警", (ref Product product) =>
			{
				product = _productRepository.Get(productId, true);

				if (product == null)
				{
					throw new HangerdException("商品信息不存在");
				}

				switch (alarmTypeDto)
				{
					case InventoryAlarmTypeDto.Quantity:
						product.SetQuantityAlarmConfig(false, null);
						break;
					case InventoryAlarmTypeDto.Expiry:
						product.SetExpiryAlarmConfig(false, null);
						break;
					default:
						throw new HangerdException("告警类别错误");
				}

				_productRepository.Update(product, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		#endregion
	}
}
